package org.duang.db.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.duang.common.exceptios.ServiceException;
import org.duang.db.IdEntity;
import org.duang.db.mongo.Operator;
import org.duang.db.mongo.parser.DecodeConvetor;
import org.duang.kit.MongoKit;
import org.duang.kit.ToolsKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;

/**
 * Mongodb的查询对象
 * @author laotang
 */
public class Query<T> {

	private final static Logger logger = LoggerFactory.getLogger(Query.class);
	
	private DBObject queryObj;
	private Order order;
	private Field field;
	private Page<T> page;
	
	private DBCollection coll;
    private Class<T> clazz;
    private DBObject keys;
	
	public Query(){
		queryObj = new BasicDBObject();
		order = new Order();
		field = new Field();
		page = new Page<T>(0,1);
	}
	
	public Query(DBCollection coll, Class<T> clazz, DBObject keys){
		this();
		this.coll = coll;
		this.clazz = clazz;
		this.keys = keys;
	}
	
	/**
	 * 等于
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> eq(String key, Object value){
		append2DBObject(key, null, value);
        return this;
	}
	
	/**
	 * 不等于
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> ne(String key, Object value){
		append2DBObject(key, Operator.NE, value);
        return this;
	}
	
	/**
	 * 大于(>)
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> gt(String key, Object value){
		append2DBObject(key, Operator.GT, value);
		return this;
	}
	
	/**
	 *  大于等于(>=)
	  * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> gte(String key, Object value){
		append2DBObject(key, Operator.GTE, value);
		return this;
	}
	
	/**
	 * 小于(<)
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> lt(String key, Object value){
		append2DBObject(key, Operator.LT, value);
		return this;
	}
	
	/**
	 * 小于等于(<=)
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> lte(String key, Object value){
		append2DBObject(key, Operator.LTE, value);
		return this;
	}
	
	/**
	 * in查询
	 * @param key		字段名
	 * @param value		内容集合
	 * @return
	 */
	public Query<T> in(String key, Object... value) {
		append2DBObject(key, Operator.IN, value);
		return this;
	}
	
	/**
	 * not in 查询
	 * @param key		字段名
	 * @param value		内容集合
	 * @return
	 */
	public Query<T> nin(String key, Object... value) {
		append2DBObject(key, Operator.NIN, value);
		return this;
	}
	
	/**
	 * 多条件or查询
	 * @param querys	条件
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Query<T> or( Query... querys) {
		List orDboList = (List)queryObj.get(Operator.OR);
		if(ToolsKit.isEmpty(orDboList)) {
			orDboList = new ArrayList();
			queryObj.put(Operator.OR, orDboList);
		}		
		for( Query q : querys ) {
			orDboList.add(q.getQueryObj());	
		}
		return this;
	}
	
	/**
	 * 多条件and查询
	 * @param querys	条件
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Query<T> and(Query... querys) {
		List andDboList = (List)queryObj.get(Operator.AND);
		if(ToolsKit.isEmpty(andDboList)) {
			andDboList = new ArrayList();
			queryObj.put(Operator.AND, andDboList);
		}
		for(Query q : querys) {
			andDboList.add(q.getQueryObj());
		}
		return this;
	}
	
	/**
	 * 模糊查询
	 * @param key		字段名
	 * @param value		内容值
	 * @return
	 */
	public Query<T> like(String key, Object value) {
		return regex(key, ".*"+value+".*");
	}
	
	/**
	 * 正则表达式查询
	 * @param key		字段名
	 * @param value		正则表达式字符串
	 * @return
	 */
	public Query<T> regex(String key, String value) {
		append2DBObject(key, null, Pattern.compile(value));
		return this;
	}
	
	/**
	 * 查询字段是否存在
	 * @param key				字段值
	 * @param value			true为存在， false为不存在
	 * @return
	 */
	public Query<T> exist(String key, boolean value) {
//		db.runCommand({distinct:"Fans",key:"_id",query:{"commUserDatas.50":{"$exists":true}}});
		append2DBObject(key, Operator.EXISTS, value);
		return this;
	}
	
	/**
	 * 对内嵌文档的多个键进行查询
	 * @param key		内嵌文档字段名
	 * @param query		查询对象
	 * @return
	 */
	public Query<T> em(String key, Query<T> query) {
		DBObject dbo = new BasicDBObject(Operator.ELEMMATCH, query.getQueryObj());
		queryObj.put(key, dbo);
		return this;
	}
	
	private void append2DBObject(String key, String oper, Object value){
		if(ToolsKit.isEmpty(key)) throw new NullPointerException("query key is null...");
//		if(ToolsUtil.isEmpty(value) && !Operator.NE.equals(oper) && oper == null) throw new NullPointerException("query value is null...");
		if(IdEntity.ID_FIELD.equals(key)){
			value = ToolsKit.message2ObjectId((String)value);
		}
		append(key, oper, value);
	}
	
	private void append2DBObject(String key, String oper, Object... value){
		if(ToolsKit.isEmpty(key)) throw new NullPointerException("query key is null...");
//		if(ToolsUtil.isEmpty(value)) throw new NullPointerException("query value is null...");
		if(IdEntity.ID_FIELD.equals(key)){
			append(key, oper, MongoKit.toObjectIds(value));
		} else {
			append(key, oper, value);
		}
	}
	
	private void append(String key, String oper, Object value) {
		if(ToolsKit.isEmpty(oper)){
			queryObj.put(key, value);		//如果没有操作符的话就全部当作等于查找
		} else {
			Object obj = queryObj.get(key);
			DBObject dbo = null;
			if(obj instanceof DBObject){
				((DBObject)obj).put(oper, value);				//追加到原来的dbo对象		
			} else {
				dbo = new BasicDBObject(oper, value);
				queryObj.put(key, dbo);
			}
		}
	}
	
	public Query<T> fields(Field field) {
		this.field = field;
		return this;
	}
	
	@Deprecated
	public Query<T> fields(Collection<String> fields) {
		for(Iterator<String> it = fields.iterator();it.hasNext();){
			field.add(it.next());
		}
		return this;
	}
	
	public Query<T> order(String fieldName, String order) {
		this.order.add(fieldName, order);
		return this;
	}
	
	public Query<T> order(Order order) {
		this.order = order;
		return this;
	}
	
	public Query<T> page(Page<T> page){
		this.page = page;
		return this;
	}
	
	@Deprecated
	public Query<T> pageNo(int pageNo){
		this.page.setPageNo(pageNo);
        return this;
    }
	@Deprecated
    public Query<T> pageSize(int pageSize){
    	this.page.pageSize(pageSize);
        return this;
    }
	
  
    
	public DBObject getQueryObj() {
		logger.debug(" query: " + queryObj.toString());
		return queryObj;
	}

	public DBObject getDBOrder() {
		DBObject orderObj = order.getDBOrder();		
		logger.debug(" order: " + orderObj.toString());
		return orderObj;
	}
	
	public DBObject getDBFields() {
		DBObject fieldObj = field.getDBFields();
		logger.debug(" fields: " + fieldObj.toString());
		return fieldObj;
	}
	
	public Page<T> getPage() {
		return page;
	}
	
    private void checkSingle(DBObject orderDbo, Page<T> page ) {
    	
        if(ToolsKit.isNotEmpty(orderDbo) || page.getPageNo()!=0 || page.getPageSize()!=0){
        	logger.error("orderBy: " + orderDbo.toString() +"       pageNo: "+ page.getPageNo() + "          pageSize: "+page.getPageSize());
            throw new ServiceException("findOne时, orderBy或pageNo或pageSize参数不能有值");
        }
    }
    
    public T result(){
    	DBObject orderDbo = getDBOrder();
    	Page<T> page = getPage();
    	try {
    		checkSingle(orderDbo, page);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return null;
		}
    	DBObject fieldsDbo = getDBFields();
		DBObject resultDbo = null;
		if(ToolsKit.isNotEmpty(fieldsDbo)){
			resultDbo = coll.findOne(queryObj, fieldsDbo);
		}else{
			resultDbo = coll.findOne(queryObj);
		}
		logger.debug("find: " + queryObj.toString());
		return DecodeConvetor.convetor(clazz, resultDbo);
    }
    
	public List<T> results() {
		DBCursor cursor = null;
		DBObject fieldsDbo = getDBFields();
		if (ToolsKit.isNotEmpty(fieldsDbo)) {
			cursor = coll.find(queryObj, fieldsDbo);
		} else {
			cursor = coll.find(queryObj, keys);
		}
		DBObject orderDbo = getDBOrder();
		if (ToolsKit.isNotEmpty(orderDbo)) {
			cursor.sort(orderDbo);
		}

		Page<T> page = getPage();
		if (page.getPageNo() > 0 && page.getPageSize() > 1) {
			cursor.skip((page.getPageNo() - 1) * page.getPageSize()).limit(page.getPageSize());
		}

		logger.debug("find: " + cursor.toString());
		return MongoKit.dBCursor2List(clazz, cursor);
	}

}
